package auto

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	logger "github.com/sirupsen/logrus"
	"gopkg.in/yaml.v3"
	"io"
	"net/http"
	"net/url"
	"os"
	"path"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"
)

var HomeDir = ""
var addressPool []string
var addressLen int

func init() {
	if HomeDir == "" {
		HomeDir, _ = os.Getwd()
	}
	result := make(map[string]bool)
	for key := range result {
		addressPool = append(addressPool, key)
	}
	addressLen = len(addressPool)
}

type SimulateUris struct {
	Scope         map[string]*ServiceScope `yaml:"scope"`
	Interval      int                      `yaml:"interval"`
	Times         int                      `yaml:"times"`
	Authorization string                   `yaml:"authorization"`
}

type ServiceScope struct {
	Server         string `yaml:"server"`
	Remark         string `yaml:"remark"`
	Uris           []*Uri `yaml:"uris"`
	SuccessCounter int32
	FailCounter    int32
	Total          int32
}

type Uri struct {
	Method         string            `yaml:"method"`
	Path           string            `yaml:"path"`
	Headers        map[string]string `yaml:"headers"`
	Data           interface{}       `yaml:"data"`
	SuccessCounter int32
	FailCounter    int32
	Server         string
}

type ApiResult struct {
	HttpCode int         `json:"httpCode"`
	Data     interface{} `json:"data"`
	Code     string      `json:"code"`
	Cmd      interface{} `json:"db"`
	Version  interface{} `json:"version"`
	Ticket   interface{} `json:"ticket"`
	Msg      string      `json:"msg"`
	Success  bool        `json:"success"`
}

func Load(filePath string) (*SimulateUris, error) {
	if filePath == "" {
		err := errors.New("invalid config file path")
		return nil, err
	}
	filePath = path.Join(HomeDir, filePath)
	logger.Infof("load config from %s ", filePath)
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = file.Close()
	}()

	conf := &SimulateUris{Times: 1}
	if err = yaml.NewDecoder(file).Decode(conf); err != nil {
		return nil, err
	} else if len(conf.Scope) == 0 {
		return nil, errors.New("测试服务数组不能为空")
	}
	for _, context := range conf.Scope {
		if strings.HasSuffix(context.Server, "/") {
			context.Server = strings.TrimSuffix(context.Server, "/")
		}
		for _, uri := range context.Uris {
			if strings.HasPrefix(uri.Path, "/") {
				uri.Path = strings.TrimPrefix(uri.Path, "/")
			}
		}
		for _, uri := range context.Uris {
			if !strings.HasPrefix(uri.Path, "http") {
				uri.Path = fmt.Sprintf("%s/%s", context.Server, uri.Path)
			}
		}
	}
	if conf.Times <= 0 {
		conf.Times = 1
	}
	if conf.Interval <= 0 {
		conf.Interval = 1000
	}
	return conf, nil
}

type Executor struct {
	Simulate      *SimulateUris
	Uris          []*Uri
	MaxConcurrent int
	GnConcurrent  int
	Ch            chan int
	Wg            *sync.WaitGroup
	HostsCounter  int
	Interval      time.Duration
	Locker        sync.Mutex
	Times         int
}

func beginRequest(simulateUris *SimulateUris) {
	defer func() {
		if err := recover(); err != nil {
			logger.Errorf("运行脚本失败 %s，程序退出", err)
		}
	}()
	var Uris []*Uri
	for serviceName, server := range simulateUris.Scope {
		for _, uriDest := range server.Uris {
			uriDest.Server = serviceName
			if len(uriDest.Headers) > 0 {
				isFindAuthorization := false
				for key, _ := range uriDest.Headers {
					if strings.EqualFold("Authorization", key) {
						isFindAuthorization = true
					}
				}
				if !isFindAuthorization && len(simulateUris.Authorization) > 0 {
					uriDest.Headers["Authorization"] = simulateUris.Authorization
				}
			}
			if !strings.EqualFold("POST", uriDest.Method) {
				if len(uriDest.Headers) > 0 {
					delete(uriDest.Headers, "Content-Type")
				}
			}
		}
		Uris = append(Uris, server.Uris...)
	}
	var wg sync.WaitGroup
	executor := Executor{Interval: time.Duration(simulateUris.Interval) * time.Millisecond, Times: simulateUris.Times * len(Uris), Uris: Uris, Simulate: simulateUris, Ch: make(chan int, 20000), MaxConcurrent: runtime.NumCPU(), GnConcurrent: runtime.NumCPU(), Wg: &wg, HostsCounter: len(simulateUris.Scope)}
	if len(executor.Uris) == 0 {
		logger.Info(" 地址不能为空或者 hosts不能为空")
		return
	}
	executor.GenUrl()
	executor.Run()
	wg.Wait()
	logger.Info("---------|----------------------统-------------------|-----------------------")
	logger.Info("---------|----------------------计-------------------|-----------------------")
	logger.Info("---------|----------------------报-------------------|-------------------------")
	logger.Info("---------|----------------------表-------------------|-------------------------")
	logger.Info("----------------------------按照请求单项请求---------------------------------------")
	for _, uriDest := range executor.Uris {
		singleServer, OK := simulateUris.Scope[uriDest.Server]
		if OK {
			singleServer.FailCounter = singleServer.FailCounter + uriDest.FailCounter
			singleServer.SuccessCounter = singleServer.SuccessCounter + uriDest.SuccessCounter
			singleServer.Total = singleServer.Total + uriDest.SuccessCounter + uriDest.FailCounter
		}
		logger.Infof("服务:%s,路径:%s 请求总数:%d,成功:%d，失败:%d", uriDest.Server, uriDest.Path, uriDest.SuccessCounter+uriDest.FailCounter, uriDest.SuccessCounter, uriDest.FailCounter)
	}
	logger.Info("----------------------------单项报表结束--------------------------------------------------")
	logger.Info("----------------------------服务请求统计报表------------------------------------------")
	for serviceName, singleServer := range simulateUris.Scope {
		logger.Infof("服务:%s 请求地址总数:%d, 请求总数:%d，成功:%d，失败:%d", serviceName, len(singleServer.Uris), singleServer.Total, singleServer.SuccessCounter, singleServer.FailCounter)
		//logger.Infof("server:%s  请求总数:%d，成功:%d，失败:%d \n", serviceName, singleServer.Total, singleServer.SuccessCounter, singleServer.FailCounter)
	}
}

func (sm *Executor) request(uri *Uri) bool {

	defer func() {
		if err := recover(); err != nil {
			logger.Errorf("%s 程序请求失败 %s ", uri.Path, err)
		}
	}()
	var contentType = "application/json"
	if len(uri.Headers) > 0 {
		contentType = uri.Headers["Content-Type"]
	}
	var startTime = time.Now()
	var req *http.Request
	var err error
	if uri.Method == "POST" {
		if strings.EqualFold(contentType, "application/json") || strings.EqualFold(contentType, "application/json,UTF-8") {
			req, err = http.NewRequest(uri.Method, uri.Path, fetchJsonData(uri.Data))
		} else if strings.EqualFold(contentType, "application/x-www-form-urlencoded") {
			req, err = http.NewRequest(uri.Method, uri.Path, fetchFormData(uri.Data))
		} else {
			req, err = http.NewRequest(uri.Method, uri.Path, nil)
		}
		if err != nil {
			logger.Errorf("%s POST 程序请求失败 %s ", uri.Path, err)
			return false
		}
	} else {
		req, err = http.NewRequest("GET", uri.Path, nil)
		if err != nil {
			logger.Errorf("%s GET程序请求失败 %s ", uri.Path, err)
			return false
		}
	}
	if len(uri.Headers) > 0 {
		for key, value := range uri.Headers {
			req.Header.Add(key, value)
		}
	}
	client := &http.Client{Timeout: sm.Interval}
	resp, err := client.Do(req)
	if err != nil {
		logger.Errorf(" 请求接口 %s,异常 %v", uri.Path, err)
		return false
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		logger.Infof("path: %s,cost: %d,status: %s ", uri.Path, time.Now().Sub(startTime).Milliseconds(), resp.Status)
		return false
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		logger.Errorf("请求接口 %s 读取响应失败异常 %v ", uri.Path, err)
	}
	bodyStr := string(body)
	if strings.HasPrefix(bodyStr, "{") && strings.HasSuffix(bodyStr, "}") && strings.Index(bodyStr, "httpCode") > 0 {
		var apiResult ApiResult
		err = json.Unmarshal(body, &apiResult)
		if err != nil {
			logger.Errorf("请求接口 %s 解析JSON失败 %v ", uri.Path, err)
			return false
		}
		logger.Infof("服务:%s,路径: %s,用时: %d毫秒, 结果:%+v", uri.Server, uri.Path, time.Now().Sub(startTime).Milliseconds(), apiResult)
		return strings.EqualFold(apiResult.Code, "2000000000") || strings.EqualFold(apiResult.Code, "200")
	}
	logger.Infof("服务:%s,路径: %s,用时: %d毫秒, 结果:%s", uri.Server, uri.Path, time.Now().Sub(startTime).Milliseconds(), bodyStr)
	return true
}
func fetchJsonData(data interface{}) *bytes.Buffer {
	if data != nil {
		jsonData, err := json.Marshal(data)
		if err != nil {
			logger.Fatalf("%v data转换为JSON字符串时发生错误:%s", data, err)
		}
		return bytes.NewBuffer(jsonData)
	}
	return bytes.NewBuffer([]byte("{}"))
}

func fetchFormData(data interface{}) *bytes.Reader {
	kindType := reflect.ValueOf(data)
	switch kindType.Kind() {
	case reflect.Map:
		destMap, ok := data.(map[string]interface{})
		if ok {
			formValues := url.Values{}
			for key, value := range destMap {
				formValues.Set(key, convertString(value))
			}
			formDataStr := formValues.Encode()
			formDataBytes := []byte(formDataStr)
			return bytes.NewReader(formDataBytes)
		}
	case reflect.Slice, reflect.Array:
		lines, _ := data.([]string)
		formValues := url.Values{}
		for _, line := range lines {
			dataList := strings.Split(line, "&")
			for _, value := range dataList {
				keyValue := strings.Split(value, "=")
				if len(keyValue) == 2 {
					formValues.Set(keyValue[0], keyValue[1])
				}
			}
		}
		formDataStr := formValues.Encode()
		formDataBytes := []byte(formDataStr)
		return bytes.NewReader(formDataBytes)
	case reflect.String:
		line, _ := data.(string)
		dataList := strings.Split(line, "&")
		formValues := url.Values{}
		if len(dataList) > 0 {
			for _, value := range dataList {
				keyValue := strings.Split(value, "=")
				if len(keyValue) == 2 {
					formValues.Set(keyValue[0], keyValue[1])
				}
			}
		}
		formDataStr := formValues.Encode()
		formDataBytes := []byte(formDataStr)
		return bytes.NewReader(formDataBytes)
	default:
		logger.Fatalf("不支持的数据内容,kind %s,data:%s", kindType, data)
	}
	return nil
}
func (sm *Executor) Run() {
	for i := 0; i < sm.MaxConcurrent; i++ {
		sm.Wg.Add(1)
		go func() {
			defer sm.Wg.Done()
			for {
				select {
				case position, ok := <-sm.Ch:
					if !ok {
						return
					}
					uri := sm.Uris[position]
					if sm.request(uri) {
						atomic.AddInt32(&uri.SuccessCounter, 1)
						fmt.Println(uri.SuccessCounter)
					} else {
						atomic.AddInt32(&uri.FailCounter, 1)
						fmt.Println(uri.FailCounter)
					}
				}
			}
		}()
	}
}

func (sm *Executor) GenUrl() {
	for i := 0; i < sm.GnConcurrent; i++ {
		sm.Wg.Add(1)
		go func() {
			defer func() {
				if err := recover(); err != nil {
					logger.Errorf("genUrl exit %s", err)
				}
				sm.Wg.Done()
			}()
			timer := time.NewTimer(sm.Interval)
			defer timer.Stop()
			for {
				select {
				case <-timer.C:
					position, err := sm.generateAddress()
					if err != nil {
						sm.Closed(sm.Ch)
						return
					}
					sm.Ch <- position
					timer.Reset(sm.Interval)
				}
			}
		}()
	}
}

func (sm *Executor) Closed(ch chan int) {
	defer func() {
		if err := recover(); err != nil {
			logger.Errorf("closed chan exit %s", err)
		}
	}()
	sm.Locker.Lock()
	defer func() {
		sm.Locker.Unlock()
	}()
	if !sm.isChanClosed(ch) {
		close(ch)
	}
}

func (sm *Executor) isChanClosed(ch interface{}) bool {
	if reflect.TypeOf(ch).Kind() != reflect.Chan {
		panic("only channels!")
	}
	cptr := *(*uintptr)(unsafe.Pointer(
		unsafe.Pointer(uintptr(unsafe.Pointer(&ch)) + unsafe.Sizeof(uint(0))),
	))
	// this function will return true if chan.closed > 0
	// see hchan on https://github.com/golang/go/blob/master/src/runtime/chan.go
	// type hchan struct {
	// cpr   uint           // total data in the queue
	// dataqsiz uint           // size of the circular queue
	// buf      unsafe.Pointer // points to an array of dataqsiz elements
	// elemsize uint16
	// closed   uint32
	// **
	cptr += unsafe.Sizeof(uint(0)) * 2
	cptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0)))
	cptr += unsafe.Sizeof(uint16(0))
	return *(*uint32)(unsafe.Pointer(cptr)) > 0
}
func (sm *Executor) generateAddress() (int, error) {
	sm.Locker.Lock()
	defer func() {
		sm.Locker.Unlock()
	}()
	uriLength := len(sm.Uris)
	Cursor := sm.Times % uriLength
	if Cursor >= uriLength {
		return -1, errors.New("")
	}
	if sm.Times <= 0 {
		return -1, errors.New("")
	}
	sm.Times = sm.Times - 1
	return Cursor, nil
}

func convertString(value interface{}) string {
	var key string
	if value == nil {
		return key
	}
	switch value.(type) {
	case float64:
		ft := value.(float64)
		key = strconv.FormatFloat(ft, 'f', -1, 64)
	case float32:
		ft := value.(float32)
		key = strconv.FormatFloat(float64(ft), 'f', -1, 64)
	case int:
		it := value.(int)
		key = strconv.Itoa(it)
	case uint:
		it := value.(uint)
		key = strconv.Itoa(int(it))
	case int8:
		it := value.(int8)
		key = strconv.Itoa(int(it))
	case uint8:
		it := value.(uint8)
		key = strconv.Itoa(int(it))
	case int16:
		it := value.(int16)
		key = strconv.Itoa(int(it))
	case uint16:
		it := value.(uint16)
		key = strconv.Itoa(int(it))
	case int32:
		it := value.(int32)
		key = strconv.Itoa(int(it))
	case uint32:
		it := value.(uint32)
		key = strconv.Itoa(int(it))
	case int64:
		it := value.(int64)
		key = strconv.FormatInt(it, 10)
	case uint64:
		it := value.(uint64)
		key = strconv.FormatUint(it, 10)
	case string:
		key = value.(string)
	case []byte:
		key = string(value.([]byte))
	default:
		newValue, _ := json.Marshal(value)
		key = string(newValue)
	}
	return key
}

/**
func (sm *Executor) generateAddress() *Uri {
	rand.Seed(time.Now().UnixNano())
	num := rand.Intn(sm.HostsCounter)
	var server string
	if num >= sm. {
		server = sm.Simulate.Server[num-1]
	} else {
		server = sm.Simulate.Server[num]
	}
	var uri Uri
	num = rand.Intn(sm.UriCounter)
	if num >= sm.UriCounter {
		uri = sm.Simulate.Uris[num-1]
	} else {
		uri = sm.Simulate.Uris[num]
	}
	uri.Path = fmt.Sprintf("%s%s", server, uri.Path)
	return &uri
}
**/
//func genAddress() string {
//	num := rand.Intn(addressLen)
//	var url string
//	if num > 0 {
//		url = addressPool[num-1]
//	} else {
//		url = addressPool[num]
//	}
//	return url
//}

//func permutation(str []byte, start int, result map[string]bool) {
//	if start == len(str)-1 {
//		result[string(str)] = true
//		return
//	}
//	for i := start; i < len(str); i++ {
//		if i != start && str[i] == str[start] {
//			continue
//		}
//		str[start], str[i] = str[i], str[start]
//		permutation(str, start+1, result)
//		str[start], str[i] = str[i], str[start]
//	}
//}
